﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EntModApp.EntServices.IoC
{
  public class CompositeServiceLocatorAdapter :
    IServiceLocator
  {
    int reentry = 0;
    private List<Lazy<IServiceLocator>> serviceLocators = new List<Lazy<IServiceLocator>>();

    public CompositeServiceLocatorAdapter()
    {
    }
    public CompositeServiceLocatorAdapter(params object[] locators)
    {
      AddRange(locators);
    }

    public void AddRange(IEnumerable<Lazy<IServiceLocator>> locators)
    {
      if (locators == null) throw new ArgumentNullException("locators");
      foreach (var item in locators)
        Add(item);
    }
    public void AddRange(IEnumerable<Func<IServiceLocator>> locators)
    {
      if (locators == null) throw new ArgumentNullException("locators");
      foreach (var item in locators)
        Add(item);
    }
    public void AddRange(IEnumerable<IServiceLocator> locators)
    {
      if (locators == null) throw new ArgumentNullException("locators");
      foreach (var item in locators)
        Add(item);
    }
    public void AddRange(IEnumerable<object> locators)
    {
      if (locators == null) throw new ArgumentNullException("locators");
      foreach (var item in locators)
        Add(item);
    }

    public void AddRange(params Lazy<IServiceLocator>[] locators)
    {
      AddRange((IEnumerable<Lazy<IServiceLocator>>)locators);
    }
    public void AddRange(params Func<IServiceLocator>[] locators)
    {
      AddRange((IEnumerable<Func<IServiceLocator>>)locators);
    }
    public void AddRange(params IServiceLocator[] locators)
    {
      AddRange((IEnumerable<IServiceLocator>)locators);
    }
    public void AddRange(params object[] locators)
    {
      AddRange((IEnumerable<object>)locators);
    }

    public void Add(Lazy<IServiceLocator> locator)
    {
      if (locator == null) throw new ArgumentNullException("locator");
      CheckReentry();
      serviceLocators.Add(locator);
    }
    public void Add(Func<IServiceLocator> lazyLocator)
    {
      if (lazyLocator == null) throw new ArgumentNullException("lazyLocator");
      Add(new Lazy<IServiceLocator>(lazyLocator));
    }
    public void Add(IServiceLocator locator)
    {
      if (locator == null) throw new ArgumentNullException("locator");
      Add(new Lazy<IServiceLocator>(() => locator));
    }
    public void Add(object locatorRef)
    {
      if (locatorRef == null) throw new ArgumentNullException("locatorRef");
      if (locatorRef is Lazy<IServiceLocator>)
        Add((Lazy<IServiceLocator>)locatorRef);
      else if (locatorRef is Func<IServiceLocator>)
        Add((Func<IServiceLocator>)locatorRef);
      else if (locatorRef is IServiceLocator)
        Add((IServiceLocator)locatorRef);
      else throw new ArgumentException("Invalid locator type");
    }

    public IEnumerable<TService> GetAllInstances<TService>()
    {
      return serviceLocators.SelectMany(l => l.Value.GetAllInstances<TService>());
    }
    public IEnumerable<object> GetAllInstances(Type serviceType)
    {
      return serviceLocators.SelectMany(l => l.Value.GetAllInstances(serviceType));
    }

    public TService GetInstance<TService>()
    {
      return serviceLocators
        .Select(l => 
          {
            return l.Value.GetInstance<TService>();
            //try { return l.Value.GetInstance<TService>(); }
            //catch { return default(TService); }
          })
        .Where(s => !Equals(s, default(TService)))
        .First();
    }
    public TService GetInstance<TService>(string key)
    {
      return serviceLocators
        .Select(l =>
        {
          return l.Value.GetInstance<TService>(key); 
          //try { return l.Value.GetInstance<TService>(key); }
          //catch { return default(TService); }
        })
        .Where(s => !Equals(s, default(TService)))
        .First();
    }

    public object GetInstance(Type serviceType)
    {
      return serviceLocators
        .Select(l =>
        {
          return l.Value.GetInstance(serviceType); 
          //try { return l.Value.GetInstance(serviceType); }
          //catch { return null; }
        })
        .Where(s => s != null)
        .First();
    }
    public object GetInstance(Type serviceType, string key)
    {
      return serviceLocators
        .Select(l =>
        {
          return l.Value.GetInstance(serviceType, key);
          //try { return l.Value.GetInstance(serviceType, key); }
          //catch { return null; }
        })
        .Where(s => s != null)
        .First();
    }

    private void CheckReentry()
    {
      if (reentry > 0) 
        throw new InvalidOperationException("Cannot modify locator while looking for services");
    }

  }
}
